
//  **** BEGIN LICENSE BLOCK ****
//  Copyright(c) 2002-2003 Daniel Savard.
//
//  LiveHTTPHeaders: this programs have two purpose
//  - Add a tab in PageInfo to show http headers sent and received
//  - Add a tool that display http headers in real time while loading pages
//
//  This program is free software; you can redistribute it and/or modify it under
//  the terms of the GNU General Public License as published by the Free
//  Software Foundation; either version 2 of the License, or (at your option)
//  any later version.
//
//  This program is distributed in the hope that it will be useful, but
//  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
//  or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
//  more details.
//
//  You should have received a copy of the GNU General Public License along with
//  this program; if not, write to the Free Software Foundation, Inc., 59 Temple
//  Place, Suite 330, Boston, MA 02111-1307 USA
//  **** END LICENSE BLOCK ****

// To enable dumpall: browser.dom.window.dump.enabled = true

// To use LiveHTTPHeaders's headers, call addObserver(obj) and removeObserver(obj)
// This will call obj.observeGRequest and obj.observeResponse for each request
// - observeRequest(uri, request)  // This doesn't exist !!!  It is for future
// - observeGRequest(uri, method)  // This is only for Generator
// - observeResponse(uri, request, response, postData, isRedirect)
if (!livehttpheaders) 
    var livehttpheaders = {};
if (!livehttpheaders.live) 
    livehttpheaders.live = {};
	
if(!screengrab)
{
	var screengrab = {};
	var sg = screengrab;
}

livehttpheaders.live.oHeaderInfoLive = null;
livehttpheaders.live.startHeaderInfoLive = function(){
    livehttpheaders.live.oHeaderInfoLive = new livehttpheaders.live.HeaderInfoLive();
    livehttpheaders.live.oHeaderInfoLive.start();
    livehttpheaders.live.addToListener(livehttpheaders.live.oHeaderInfoLive)
}
livehttpheaders.live.stopHeaderInfoLive = function(){
    livehttpheaders.live.removeFromListener(livehttpheaders.live.oHeaderInfoLive)
    livehttpheaders.live.oHeaderInfoLive.stop();
    livehttpheaders.live.oHeaderInfoLive = null;
}

livehttpheaders.live.addToListener = function(obj){
    // Register new request and response listener
    if ('nsINetModuleMgr' in Components.interfaces) {
        // Should be an old version of Mozilla/Phoenix (before september 15, 2003)
        var netModuleMgr = Components.classes["@mozilla.org/network/net-extern-mod;1"].getService(Components.interfaces.nsINetModuleMgr);
        netModuleMgr.registerModule("@mozilla.org/network/moduleMgr/http/request;1", obj);
        netModuleMgr.registerModule("@mozilla.org/network/moduleMgr/http/response;1", obj)
    }
    else {
        // Should be a new version of  Mozilla/Phoenix (after september 15, 2003)
        var observerService = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
        observerService.addObserver(obj, "http-on-modify-request", false);
        observerService.addObserver(obj, "http-on-examine-response", false);
        observerService.addObserver(obj, "StartDocumentLoad", false);
        observerService.addObserver(obj, "EndDocumentLoad", false);
        observerService.addObserver(obj, "FailDocumentLoad", false);
        
        //+++
        //window.addEventListener("onStateChange", clickHandler, false);
        //window.addEventListener("load", clickHandler, false);
        //window.addEventListener("mousedown", clickHandler, false);
        //window.addEventListener("click", clickHandler, false);
    }
}
livehttpheaders.live.removeFromListener = function(obj){
    // Unregistering listener
    if ('nsINetModuleMgr' in Components.interfaces) {
        // Should be an old version of Mozilla/Phoenix (before september 15, 2003)
        var netModuleMgr = Components.classes["@mozilla.org/network/net-extern-mod;1"].getService(Components.interfaces.nsINetModuleMgr);
        netModuleMgr.unregisterModule("@mozilla.org/network/moduleMgr/http/request;1", obj);
        netModuleMgr.unregisterModule("@mozilla.org/network/moduleMgr/http/response;1", obj);
    }
    else {
        // Should be a new version of  Mozilla/Phoenix (after september 15, 2003)
        var observerService = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
        observerService.removeObserver(obj, "http-on-modify-request");
        observerService.removeObserver(obj, "http-on-examine-response");
        observerService.removeObserver(obj, "StartDocumentLoad");
        observerService.removeObserver(obj, "EndDocumentLoad");
        observerService.removeObserver(obj, "FailDocumentLoad");
    }
}

livehttpheaders.live.HeaderInfoLive = function(){
    this.test = new Array(); //Test
    this.data = new Array(); //Data for each row
    this.type = new Array(); //Type of data (request, post, response, url, etc)
    this.style = new Array(); //Style of data (request, post, response, url, etc)
    this.check = new Array(); //This is an array of url to check (modify headers)
    this.names = new Array(); //Names for tree's images
    this.atoms = new Array(); //Atoms for tree's styles
    this.observers = new Array(); // LiveHTTPHeaders's Observers
    this.initXMLStream();
    this.RAurls = new Array();
    this.RAmethods = new Array();
    this.RAversions = new Array();
    this.RAtimes = new Array(); // Document Thinking times
    this.RAusercls = new Array(); // If the object was requested by a user click
    this.RArequests = new Array();
    this.RApostdatas = new Array();
	this.RABrowserStates = new Array(); //contains browser states for each document
    this.replayingAll = 0;
    this.replayAllCounter = 0;
    this.replayAllTotal = 0;
	this.currentBrowserState = 0;
	this.currentDocument = 0;
	this.nbDocuments = 0;
    this.storeFilesCounter = 0;
    this.RAtimeouts = new Array();
    this.RATimeOut = 60000;	
	this.browserStateCaptureTimer = 0;//timer for saving browser state
	
    document.getElementById("headerstatusbar-numdocs").value = "0";
    document.getElementById("headerstatusbar-numobjs").value = "0";
    this.setLastAction("Stopped eCARENA");
    
    // Read preferences
    this.pref = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService);
    this.epref = this.pref.getBranch("extensions.ecarena."); // eCARENA pref
    this.mpref = this.pref.getBranch(""); // Mozilla pref
    this.browserStateCaptureInterval = this.getIntPref(this.epref, "browserStateCaptureInterval", 2000); // interval between 2 captures of the browser state
    this.usestyle = this.getIntPref(this.epref, "style", 0); // Use style sheet
    this.mode = this.getIntPref(this.epref, "mode", 1); // Post capture mode
    this.usefilter = this.getBoolPref(this.epref, "filter", false); // Filter mode
    this.filterRegexp = this.getCharPref(this.epref, "filterRegexp", '/$|\.html$'); // Filter mode
    this.useexclude = this.getBoolPref(this.epref, "exclude", false); // exclude mode
    this.excludeRegexp = this.getCharPref(this.epref, "excludeRegexp", '\.gif$|\.jpg$|\.ico$|\.css$|\.js$'); // exclude mode
    this.savexml = this.getBoolPref(this.epref, "savexml", true); // Save in XML rich format
    this.storefilesra = this.getBoolPref(this.epref, "storefilesra", false); // Store all files
    this.storefilesdirra = this.getCharPref(this.epref, "storefilesdirra", 'c:\\temp-ecarena'); // Directory where ReplayAll files will be stored
    this.storefilesdirraregexp = this.getCharPref(this.epref, "storefilesdirraregexp", '\.zip$|\.pdf$|\.rar$|\.avi$|\.mov$|\.mp3$'); // Regexp of files to download
    this.notClearUrlInfo = this.getBoolPref(this.epref, "notClearUrlInfo", false);
    this.clearCacheReplayAll = this.getBoolPref(this.epref, "clearCacheReplayAll", false); // Automatically clear the cache when replaying all
    this.reqAsUserReplayAll = this.getBoolPref(this.epref, "reqAsUserReplayAll", true); // Request only user objects
    this.temporizePerUserReplayAll = this.getBoolPref(this.epref, "temporizePerUserReplayAll", true); // Request at the same rate the user
}
livehttpheaders.live.HeaderInfoLive.prototype = {
    // Constants
    // Type
    URL: 1,
    REQUEST: 2,
    POSTDATA: 3,
    RESPONSE: 4,
    REQSPACE: 5,
    RESSPACE: 6,
    SEPARATOR: 7,
    // Style
    FIRST: 100,
    MID: 200,
    LAST: 300,
    SINGLE: 400,
    // Strings
    SEPSTRING: "----------------------------------------------------------\r\n",
    
    test: null,
    oDump: null,
    isCapturing: false,
    // XML format:
    XCARENA: "CARENA",
    
    XOBJECT: "object",
    XURL: "url",
    XURL_REDIRECT: "url-redirect",
    XMETHOD: "method",
    XVERSION: "version",
    XOBJECT_SIZE: "size",
    XREFERRER: "referrer",
    XLATENCY: "latency",
    XREQUEST: "request",
    XREQUEST_HEADER: "reqHeader",
    XREQUEST_LENGHT: "requestL",
    XREQUEST_TIME: "requestT",
    XRESPONSE: "response",
    XRESPONSE_HEADER: "respHeader",
    XRESPONSE_LENGHT: "responseL",
    XRESPONSE_TIME: "responseT",
    XPOST: "post",
    XUSER_CLICKED: "usercl",
    XTHINKING_TIME: "thinkingT",
    XSTATUS: "status",
    XPREFETCH: "prefetch",
    
    XNUMOBJECTS: "numObjects",
    //XNUMNONRESPONSES : "numNonResponses",
    //XNUMOBJECTS_NOTREC : "docNumObjectsNotReceived", //+++ badlop to remove
    
    XDOCUMENT: "document",
    XDOC_START: "timeStart",
    XDOC_END: "timeEnd",
    XDOC_LATENCY: "docLatency",
    XDOC_STATUS: "docStatus",
    XDOC_THINKTIME: "docThinkTime",
    XDOC_FROMFRAME: "docFromFrame", // The object that starts the document was requested inside a frame
    XDOC_REFERRER: "docReferrer",
    
    XDOC_REMOVEME: "removeme", // this document what created manually because of errors, it must be removed
    XFRAME: "frame",
	
	XBROWSER_STATES: "browserStates",
	XBROWSER_STATE: "browserState", // the browser state is a "snapshot" of the browser at a given time ( window size, mouse/scrollbar position, etc)
    XNUMSTATES	: "numStates",
		
	XMOUSE: "mouse",
	XWINDOW: "window",
	XSCROLLBAR : "scrollbar",
	
	XPOSITION_X: "xPos",
	XPOSITION_Y: "yPos",		
	XWIDTH  : "width",
	XHEIGHT : "height",
	XLEFT: "left",
	XTOP: "top",
	
	XINTERVAL: "interval",
	
    // Tree interface
    rows: 0,
    tree: null,
    
    // Horizontal scrolling
    hScrollBar: null,
    hScrollPos: 0,
    hScrollMax: 0,
    datapresent: "datapresent",
    replayall: "replayall",
    replayallNo: "replayallNo",
    
     set rowCount(c){
        throw "rowCount is a readonly property";
    },
     get rowCount(){
        return this.rows;
    },
    setTree: function(tree){
        this.tree = tree;
    },
    getCellText: function(row, column){
        //return this.data[row].match(/^.*/);
        return this.data[row].substr(this.hScrollPos).match(/^.*/);
    },
    setCellText: function(row, column, text){
    },
    getRowProperties: function(row, props){
    },
    getCellProperties: function(row, col, props){
        if (this.usestyle) {
            props.AppendElement(this.atoms[this.type[row] + this.style[row]]);
        }
    },
    getColumnProperties: function(column, elem, prop){
    },
    isContainer: function(index){
        return false;
    },
    isContainerOpen: function(index){
    },
    isContainerEmpty: function(index){
        return false;
    },
    isSeparator: function(index){
        if (this.usestyle) {
            return false;
        }
        else {
            return this.type[index] == this.SEPARATOR;
        }
    },
    isSorted: function(){
    },
    canDropOn: function(index){
        return false;
    },
    canDropBeforeAfter: function(index, before){
        return false;
    },
    drop: function(row, orientation){
        return false;
    },
    getParentIndex: function(index){
        return 0;
    },
    hasNextSibling: function(index, after){
        if (this.type[index + 1] == this.POSTDATA) 
            return true;
        return false;
    },
    getLevel: function(index){
        if (this.type[index] == this.POSTDATA) 
            return 1;
        return 0;
    },
    getImageSrc: function(row, column){
    },
    getProgressMode: function(row, column){
    },
    getCellValue: function(row, column){
    },
    toggleOpenState: function(index){
    },
    cycleHeader: function(col, elem){
    },
    selectionChanged: function(){
    },
    cycleCell: function(row, column){
    },
    isEditable: function(row, column){
        return false;
    },
    performAction: function(action){
    },
    performActionOnRow: function(action, row){
    },
    performActionOnCell: function(action, row, column){
    },
    
    // Tree utility functions
    addRow: function(row, type){
        // Compute the style
        var style;
        if (this.rows > 0) {
            var ptype = this.type[this.rows - 1];
            var pstyle = this.style[this.rows - 1];
            if (type != ptype) {
                // We have a new type.  The line above may be single or last.
                if (pstyle == this.FIRST) {
                    pstyle = this.SINGLE;
                }
                else {
                    pstyle = this.LAST;
                }
                this.style[this.rows - 1] = pstyle; // Update the style for the line above
                style = this.FIRST;
            }
            else {
                // Same type.  This line should be middle
                style = this.MID;
            }
        }
        else {
            // The first line.  No doubts, it should be of style FIRST.
            style = this.FIRST;
            if (!this.replayingAll) 
                document.getElementById(this.datapresent).removeAttribute('disabled');
        }
        
        // Add the row
        this.type.push(type);
        this.style.push(style);
        this.rows = this.data.push(row);
        if (row.length > this.hScrollMax) 
            this.sethScroll(row.length);
        
    }, //A
    rowCountChanged: function(index, count) //A
    {
        var lvr = this.tree.getLastVisibleRow();
        this.tree.rowCountChanged(index, count);
        // If the last line of the tree is visible on screen, we will autoscroll
        if (lvr >= index) 
            this.tree.ensureRowIsVisible(this.rows - 1);
    },
    
    // Horizontal scrolling functions
    sethScroll: function(max){
        // Set the new maximum value and page increment to be 5 steps
        var maxpos = this.hScrollBar.attributes.getNamedItem("maxpos");
        var pageincrement = this.hScrollBar.attributes.getNamedItem("pageincrement");
        maxpos.value = (max > 2 ? max - 3 : 0);
        pageincrement.value = max / 5;
        this.hScrollMax = max;
    },
    hScrollHandler: function(){
        // Need to use global oHeaderInfoLive object  this because 'this'
        // doesn't seem to be available.
        var base = livehttpheaders.live.oHeaderInfoLive;
        var curpos = base.hScrollBar.attributes.getNamedItem("curpos").value;
        if (curpos != base.hScrollPos) {
            base.hScrollPos = curpos;
            base.tree.invalidate();
        }
    },
    
    // Select and copy function
    onselect: function(){
        var replay = document.getElementById("headerinfo-replay");
        if (!replay) 
            return;
        var selection = this.oDump.view.selection;
        // Look if there is an URL block selected
        if (selection.count == 1 && this.type[selection.currentIndex] != this.SEPARATOR) {
            replay.disabled = false;
        }
        else {
            replay.disabled = true;
        }
    },
    
    selectBlock: function(){
        var selection = this.oDump.view.selection;
        var index = selection.currentIndex;
        if (index >= 0 && this.type[index] != this.SEPARATOR) {
            var first = index;
            var last = index;
            while (first > 0 && this.type[first - 1] != this.SEPARATOR) 
                first--;
            while (last < this.rowCount && this.type[last + 1] != this.SEPARATOR) 
                last++;
            selection.rangedSelect(first, last, false);
        }
    },
    
    selectAll: function(){
        return this.oDump.view.selection.selectAll();
    },
    
    toClipboard: function(data){
        livehttpheaders.info.toClipboard(data);
        // clipboard helper
        try {
            const clipboardHelper = Components.classes["@mozilla.org/widget/clipboardhelper;1"].getService(Components.interfaces.nsIClipboardHelper);
            clipboardHelper.copyString(data);
        } 
        catch (e) {
            // do nothing, later code will handle the error
            dump("Unable to get the clipboard helper\n");
        }
    },
    
    copy: function(){
        this.toClipboard(this.getSelection());
    },
    
    getSelection: function(){
        var selection = this.oDump.view.selection;
        var data = "", rStart = {}, rEnd = {};
        var ranges = selection.getRangeCount();
        for (var range = 0; range < ranges; range++) {
            selection.getRangeAt(range, rStart, rEnd);
            if (rStart.value >= 0 && rEnd.value >= 0) {
                for (var row = rStart.value; row <= rEnd.value; row++) {
                    if (this.type[row] == this.SEPARATOR) {
                        data += this.SEPSTRING; // This is a separator
                    }
                    else {
                        data += this.data[row];
                    }
                }
            }
        }
        return data;
    },
    
    getAll: function(){
        var data = "";
        for (var row = 0; row < this.rows; row++) {
            if (this.type[row] == this.SEPARATOR) {
                data += this.SEPSTRING; // This is a separator
            }
            else {
                data += this.data[row];
            }
        }
        return data;
    },
    
    // Preferences function for LiveHTTPHeaders
    getIntPref: function(branch, name, value){
        if (branch.prefHasUserValue(name)) {
            return branch.getIntPref(name);
        }
        else {
            this.setIntPref(branch, name, value);
            return value;
        }
    },
    setIntPref: function(branch, name, value){
        branch.setIntPref(name, value);
    },
    getBoolPref: function(branch, name, value){
        if (branch.prefHasUserValue(name)) {
            return branch.getBoolPref(name);
        }
        else {
            this.setBoolPref(branch, name, value);
            return value;
        }
    },
    setBoolPref: function(branch, name, value){
        branch.setBoolPref(name, value);
    },
    
    getCharPref: function(branch, name, value){
        if (branch.prefHasUserValue(name)) {
            return branch.getCharPref(name);
        }
        else {
            this.setCharPref(branch, name, value);
            return value;
        }
    },
    setCharPref: function(branch, name, value){
        branch.setCharPref(name, value);
    },
    
    // Initialisation and termination functions
    start: function(){
        this.oDump = document.getElementById("headerinfo-dump");
        this.oDump.treeBoxObject.view = this;
        
        // Set configuration tab
        try {
            document.getElementById("headerinfo-mode").selectedIndex = this.mode;
            document.getElementById("headerinfo-style").checked = this.usestyle;
            document.getElementById("headerinfo-filter").checked = this.usefilter;
            document.getElementById("headerinfo-filterRegexp").value = this.filterRegexp;
            document.getElementById("headerinfo-exclude").checked = this.useexclude;
            document.getElementById("headerinfo-excludeRegexp").value = this.excludeRegexp;
            document.getElementById("headerinfo-storefilesra").checked = this.storefilesra;
            document.getElementById("headerinfo-storefilesdirra").value = this.storefilesdirra;
            document.getElementById("headerinfo-storefilesdirraregexp").value = this.storefilesdirraregexp;
            document.getElementById("headerinfo-savexml").checked = this.savexml;
            document.getElementById("headerinfo-notClearUrlInfo").checked = this.notClearUrlInfo;
            document.getElementById("headerinfo-clearCacheReplayAll").checked = this.clearCacheReplayAll;
            document.getElementById("headerinfo-reqAsUserReplayAll").checked = this.reqAsUserReplayAll;
            document.getElementById("headerinfo-temporizePerUserReplayAll").checked = this.temporizePerUserReplayAll;
        } 
        catch (ex) {
        }
        
        // Set scrollbar
        this.hScrollBar = document.getElementById("headerinfo-dump-scroll");
        setInterval(this.hScrollHandler, 100);
        
        this.initAtoms();
    },
    
    initAtoms: function(){
        // Pre-generate the atoms
        var aserv = Components.classes["@mozilla.org/atom-service;1"].createInstance(Components.interfaces.nsIAtomService);
        this.atoms[this.URL + this.SINGLE] = aserv.getAtom("URL");
        this.atoms[this.REQUEST + this.FIRST] = aserv.getAtom("FirstRequest");
        this.atoms[this.REQUEST + this.MID] = aserv.getAtom("MidRequest");
        this.atoms[this.REQUEST + this.LAST] = aserv.getAtom("LastRequest");
        this.atoms[this.REQUEST + this.SINGLE] = aserv.getAtom("SingleRequest");
        this.atoms[this.POSTDATA + this.FIRST] = aserv.getAtom("FirstPostData");
        this.atoms[this.POSTDATA + this.MID] = aserv.getAtom("MidPostData");
        this.atoms[this.POSTDATA + this.LAST] = aserv.getAtom("LastPostData");
        this.atoms[this.POSTDATA + this.SINGLE] = aserv.getAtom("SinglePostData");
        this.atoms[this.RESPONSE + this.FIRST] = aserv.getAtom("FirstResponse");
        this.atoms[this.RESPONSE + this.MID] = aserv.getAtom("MidResponse");
        this.atoms[this.RESPONSE + this.LAST] = aserv.getAtom("LastResponse");
        this.atoms[this.RESPONSE + this.SINGLE] = aserv.getAtom("SingleResponse");
        this.atoms[this.REQSPACE + this.SINGLE] = aserv.getAtom("SpaceRequest");
        this.atoms[this.RESSPACE + this.SINGLE] = aserv.getAtom("SpaceResponse");
        this.atoms[this.SEPARATOR + this.SINGLE] = aserv.getAtom("Separator");
        this.atoms[this.SEPARATOR + this.FIRST] = aserv.getAtom("Separator");
    },
    
    generateNamesAndAtoms: function(as, typeno, typename){
        this.names[typeno + this.FIRST] = "First" + typename;
        this.atoms[typeno + this.FIRST] = as.getAtom(this.names[typeno + this.FIRST]);
    },
    
    stop: function(){
        this.oDump.treeBoxObject.view = null;
        this.oDump = null;
        this.hScrollBar = null;
    },
    
    // This is the observerService's observe listener.
    observe: function(aSubject, aTopic, aData){
        if (aTopic == 'http-on-modify-request') {
            aSubject.QueryInterface(Components.interfaces.nsIHttpChannel);
            // If the object is favicon.ico and has no referrer, don't treat it 
            if ((aSubject.referrer == null)) {
                var url = aSubject.URI.asciiSpec;
                if (url.substring(url.length - 12, url.length) == "/favicon.ico") 
                    return;
            }
            
            // badlop test QQFQ
            var url2 = aSubject.URI.asciiSpec;
            //alert("url: " + url2);
            //if (url2 == "http://msdn.microsoft.com/library/shared/searchtab/search.asp?stcfg=/library/searchtabconfig.xml&dtcfg=/library/deeptreeconfig.xml&url=/library/en-us/default.asp?frame=true")
            //alert("url2 detected: " + url2);
            //http://msdn.microsoft.com/library/shared/searchtab/search.asp?stcfg=/library/searchtabconfig.xml&amp;dtcfg=/library/deeptreeconfig.xml&amp;url=/library/en-us/default.asp?frame=true
            
            if (this.isCapturing) 
                this.objStartLoad(aSubject, aTopic, aData);
            this.onModifyRequest(aSubject);
        }
        else 
            if (aTopic == 'http-on-examine-response') {
                aSubject.QueryInterface(Components.interfaces.nsIHttpChannel);
                // If the object is favicon.ico and has no referrer, don't treat it 
                if ((aSubject.referrer == null)) {
                    var url = aSubject.URI.asciiSpec;
                    if (url.substring(url.length - 12, url.length) == "/favicon.ico") 
                        return;
                }
                this.onExamineResponse(aSubject);
                if (this.isCapturing) 
                    this.objEndLoad(aSubject, aTopic, aData);
            }
            else 
                if (aTopic == 'StartDocumentLoad') {
                    if (this.isCapturing) 
                        this.docStartLoad(aData, 0);
                }
                else 
                    if (aTopic == 'EndDocumentLoad' || aTopic == 'FailDocumentLoad') {
                        if (this.isCapturing) 
                            this.docEndLoad(aTopic, aData);
                    }
                    else 
                        alert("Unknown aTopic: " + aTopic);
    },
    
    // Add a new LiveHTTPHeaders's observer
    addObserver: function(obj){
        this.observers.push(obj);
    },
    // Remove a LiveHTTPHeaders's observer
    removeObserver: function(obj){
        for (observer in this.observers) {
            if (this.observers[observer] == obj) {
                delete this.observers[observer];
            }
        }
    },
    
    // Header Info Lives functions
    capture: function(flag){
        this.isCapturing = flag;
        if (flag) 
            document.getElementById("headerstatusbar-status").value = "Capturing";
        else 
		{
            document.getElementById("headerstatusbar-status").value = "Stopped";
			clearInterval(this.browserStateCaptureTimer);
		}
    },
    
    clear: function(){
        var oldrows = this.rows;
        this.rows = 0;
        this.data = new Array();
        this.type = new Array();
        this.style = new Array();
        this.rowCountChanged(0, -oldrows);
        this.hScrollMax = 0;
        this.sethScroll(0);
        this.initXMLStream();
        this.setLastAction("Cleared Window");
        document.getElementById(this.datapresent).setAttribute('disabled', 'true');
        document.getElementById("headerstatusbar-numdocs").value = "0";
        document.getElementById("headerstatusbar-numobjs").value = "0";
    },
    clearSessionRA: function(){
        this.RAurls = new Array();
        this.RAmethods = new Array();
        this.RAversions = new Array();
        this.RAtimes = new Array(); // Timestamp
        this.RAusercls = new Array(); // If the object was requested by a user click
        this.RArequests = new Array();
        this.RApostdatas = new Array();
    },
    clearSession: function(){
        this.clear();
        this.clearSessionRA();
    },
    clearCache: function(){
        // Clear disk and memory cache
        // code taken from Mozilla\chrome\content\communicator\pref\pref-cache.js prefClearDiskAndMemCache()
        var classID = Components.classes["@mozilla.org/network/cache-service;1"];
        var cacheService = classID.getService(Components.interfaces.nsICacheService);
        cacheService.evictEntries(Components.interfaces.nsICache.STORE_ON_DISK);
        cacheService.evictEntries(Components.interfaces.nsICache.STORE_IN_MEMORY);
        this.setLastAction("Cleared cache");
    },
    disableButtonsReplayAll: function(){
        //document.getElementById("headerinfo-import").disabled = true;
        //document.getElementById("headerinfo-saveall").disabled = true;
        //document.getElementById("headerinfo-replay").disabled = true;
        //document.getElementById("headerinfo-replayAll").disabled = true;
		document.getElementById("ecarena-toolbar-button-record").setAttribute('disabled', 'true');
		document.getElementById("ecarena-toolbar-button-record").setAttribute('checked', 'true');
		this.isCapturing = true;
        document.getElementById(this.datapresent).setAttribute('disabled', 'true');
    },
    enableButtonsReplayAll: function(){
        //document.getElementById("headerinfo-import").disabled = false;
        //document.getElementById("headerinfo-saveall").disabled = false;
        //document.getElementById("headerinfo-replay").disabled = false;
        //document.getElementById("headerinfo-replayAll").disabled = false;
		document.getElementById("ecarena-toolbar-button-record").setAttribute('disabled', 'false');
		document.getElementById("ecarena-toolbar-button-record").setAttribute('checked', 'false');
		this.isCapturing = false;
        document.getElementById(this.datapresent).removeAttribute('disabled');
    },
    
    saveAll: function(title){
        if (!title) 
            title = "Save the captured session into file";
        
        if (this.savexml) {
            // TO-DO FRAMES
            // Remove all documents that should be removed
            var documents = this.LHHXML.getElementsByTagName(this.XDOCUMENT);
            for (var i = 0; i < documents.length; i++) 
                if (documents[i].getAttribute(this.XDOC_REMOVEME) == "1") 
                    this.LHHXML.removeChild(documents[i]);
            
            var prettyDataXMLString = new XMLSerializer().serializeToString(this.dataXML);
            prettyDataXMLString = prettyDataXMLString.replace(/^<\?xml\s+version\s*=\s*(["'])[^\1]+\1[^?]*\?>/, ""); // bug 336551 in E4X
            prettyDataXMLString = XML(prettyDataXMLString).toXMLString();
            
            livehttpheaders.info.saveAs("<?xml version='1.0' ?>\n" + prettyDataXMLString, title);
            this.setLastAction("Session saved in XML rich format");
        }
        else {
            livehttpheaders.info.saveAs(this.getAll(), title);
            this.setLastAction("Session saved in text format");
        }
    },
    
    saveSelection: function(title){
        livehttpheaders.info.saveAs(this.getSelection(), title);
    },
    
    replay: function(){
        var method = null, url = null, version = null, request = null, postdata = null;
        
        var selection = this.oDump.view.selection;
        var index = selection.currentIndex;
        if (index >= 0 && this.type[index] != this.SEPARATOR) {
            var first = index;
            var data;
            while (first > 0 && this.type[first - 1] != this.SEPARATOR) 
                first--;
            for (var i = first; this.type[i] != this.SEPARATOR; i++) {
                data = this.data[i];
                //dump("DATA: " + data +"\n");
                switch (this.type[i]) {
                    case this.URL:
                        url = data;
                        break;
                    case this.REQUEST:
                        if (!method) {
                            var tmp = data.match(/^(\S+).*\/(\S+)/);
                            method = tmp[1];
                            version = tmp[2];
                        }
                        else {
                            request ? request = request + data : request = data;
                        }
                        break;
                    case this.POSTDATA:
                        postdata ? postdata = postdata + data : postdata = data;
                        break;
                }
            }
            var replaywindow = "chrome://ecarena/content/livehttpheaders/LiveHTTPReplay.xul";
            var replayoptions = "chrome,dialog=no,extrachrome,menubar,resizable,scrollbars,status,toolbar";
            window.openDialog(replaywindow, "_blank", replayoptions, this, method, url, version, request, postdata);
        }
    },
    play: function(method, url, version, request, postdata){
        //DSMT: REPLAY
        //dumpall("OPENER",window.opener,2);

        var browser = (window.opener == null)? window.getWebNavigation() : window.opener.getWebNavigation();
        var mustValidate = false;
        if (browser) {
            // Change the request to an array
            var tmp = request.split(/[\r\n]/);
            var req = new Array();
            req['_METHOD_'] = method;
            for (var i in tmp) {
                //dump("Try: " + tmp[i] +"\n");
                var header = tmp[i].match(/^([^:]+)\s*:\s*(.*)$/);
                if (header) {
                    //dump("Found: " + header[1] +"="+header[2]+"\n");
                    if (!header[1].match(/^(Content-Length)$/)) 
                        req[header[1]] = header[2];
                    if (header[1].match(/^(If-Modified-Since|If-None-Match)$/)) 
                        mustValidate = true;
                }
            }
            // Change the post data to an InputStream
            var post = null;
            if (postdata != null) {
                const STRING_STREAM_CID = "@mozilla.org/io/string-input-stream;1";
                const nsIStringInputStream = Components.interfaces.nsIStringInputStream;
                //const SCRIPTABLE_STREAM_CID = "@mozilla.org/scriptableinputstream;1";
                //const nsIScriptableInputStream = Components.interfaces.nsIScriptableInputStream;
                const MIME_STREAM_CID = "@mozilla.org/network/mime-input-stream;1";
                const nsIMIMEInputStream = Components.interfaces.nsIMIMEInputStream;
                
                var sis = Components.classes[STRING_STREAM_CID];
                tmp = sis.createInstance(nsIStringInputStream);
                //postdata = "\r\n" + postdata; // Add header separator at begining
                tmp.setData(postdata, postdata.length);
                tmp.QueryInterface(Components.interfaces.nsISeekableStream);
                
                // Create a scriptable stream
                //var scis = Components.classes[SCRIPTABLE_STREAM_CID];
                //post = scis.createInstance(nsIScriptableInputStream);
                //post.init(tmp);
                
                // Create a mime stream
                var mis = Components.classes[MIME_STREAM_CID];
                post = mis.createInstance(nsIMIMEInputStream);
                post.setData(tmp);
                post.addContentLength = true;
                //if ('Content-Type' in req)
                //  post.addHeader('Content-Type', req['Content-Type']);
            
                //post.QueryInterface(Components.interfaces.nsIInputStream);
                //dumpall("POST",post,2);
                //dump("POST: available: " + post.available() + "\n");
                //dump("POST: data: '" + post.read(post.available()) + "'\n");
                //tmp.seek(0,0);
                //for (var i in Components.classes) {
                //  dump("Classes: " + i + "\n");
                //}
                //for (var o in Components.interfaces) {
                //  dump("Interfaces: " + o + "\n");
                //}
                //post=tmp;
            }
            this.check[url] = req;
            
            // And load the URL
            // Note that there may be some problems if the server answer with:304 use local copy... 
            if (mustValidate) {
                browser.loadURI(url, Components.interfaces.nsIRequest.MUST_VALIDATE, null, post, null);
            }
            else {
                if (this.notClearUrlInfo == false) {
                    // Must clear the url we want to replay from the cache
                    var classID = Components.classes["@mozilla.org/network/cache-service;1"];
                    var cacheService = classID.getService(Components.interfaces.nsICacheService);
                    
                    try {
                    
                        var cacheSession = cacheService.createSession('HTTP', 0, true);
                        //var cacheSession = cacheService.createSession('HTTP-memory-only',1,true);
                        var cacheEntryDescriptor = cacheSession.openCacheEntry(url.match(/^[^#]+/), Components.interfaces.nsICache.ACCESS_WRITE, true);
                        cacheEntryDescriptor.doom();
                        cacheEntryDescriptor.close();
                    } 
                    catch (ex) {
                        // Not able to clear disk cache selectivly, clear all of it!
                        cacheService.evictEntries(Components.interfaces.nsICache.STORE_ON_DISK);
                    }
                    
                    // Must clear memory cache because the above tips doesn't work for memory...
                    cacheService.evictEntries(Components.interfaces.nsICache.STORE_IN_MEMORY);
                }
                
                browser.loadURI(url, 0, null, post, null);
            }
        }
    },
    initXMLStream: function(){
        var gParser = new DOMParser;
        this.dataXML = gParser.parseFromString("<?xml version='1.0' ?><CARENA></CARENA>", 'text/xml');
        this.LHHXML = this.dataXML.getElementsByTagName(this.XCARENA).item(0);
    },
    
    // 'Replay All' process has ended
    replayAllEnd: function(){
        //alert("Replay done");
        this.enableButtonsReplayAll();
        document.getElementById(this.replayall).setAttribute('disabled', 'true');
        document.getElementById(this.replayallNo).removeAttribute('disabled');
        this.replayingAll = 0;
        document.getElementById("headerstatusbar-status").value = "Capturing";
        this.setLastAction("End of Replay All");
    },
    
    // 'Replay All' process aborted
    replayAllAbort: function(){
        var browser = (window.opener == null)? window.getWebNavigation() : window.opener.getWebNavigation();
        browser.stop(Components.interfaces.nsIWebNavigation.STOP_NETWORK);
        this.clearSessionRA();
        this.replayAllEnd();
        this.setLastAction("End of Replay All: Manually aborted");
    },
	// Replay the current browser state
	replayBrowserState: function(){
		
		//we get the current browser state
		var browserStateElement = this.RABrowserStates[this.currentDocument].getElementsByTagName(this.XBROWSER_STATE).item(this.currentBrowserState);
		
		//mouse state
		var mouseState = browserStateElement.getElementsByTagName(this.XMOUSE).item(0);
		var xMouse = parseInt(mouseState.getAttribute(this.XPOSITION_X));
		var yMouse = parseInt(mouseState.getAttribute(this.XPOSITION_Y));
		
		//window state
		var windowState = browserStateElement.getElementsByTagName(this.XWINDOW).item(0);
		var width = parseInt(windowState.getAttribute(this.XWIDTH));
		var height = parseInt(windowState.getAttribute(this.XHEIGHT));
		var xWindow = parseInt(windowState.getAttribute(this.XPOSITION_X));
		var yWindow = parseInt(windowState.getAttribute(this.XPOSITION_Y));
		
		//scrollbar state
		var scrollbarState = browserStateElement.getElementsByTagName(this.XSCROLLBAR).item(0);
		var left = parseInt(scrollbarState.getAttribute(this.XLEFT));
		var top = parseInt(scrollbarState.getAttribute(this.XTOP));
		
		
		//replaying state
		
		window.resizeTo(width,height);
		window.moveTo(xWindow,yWindow);
		window.scrollTo(left,top);		
		
		//we planify the replay of the next browser state
		if(this.currentBrowserState + 1 < parseInt(this.RABrowserStates[this.currentDocument].getAttribute(this.XNUMSTATES)))
		{
			var nextBrowserState = this.RABrowserStates[this.currentDocument].getElementsByTagName(this.XBROWSER_STATE).item(this.currentBrowserState+1);		
			var delay =  parseInt(nextBrowserState.getAttribute(this.XINTERVAL));			
			setTimeout("livehttpheaders.live.oHeaderInfoLive.replayBrowserState()", delay);		
		}
		
		this.currentBrowserState++;
	},
    
    // Planify a timeout for just in case an object request does not receive response
    setLastAction: function(string){
        var time = new Date();
        document.getElementById("headerstatusbar-lastaction").value = "[" + time.getHours() + ":" + time.getMinutes() + ":" + time.getSeconds() + "] " +
        string;
    },
    
    // Planify a timeout for just in case an object request does not receive response
    startRATimeOut: function(i){
        this.RAtimeouts[i] = 1;
        setTimeout("livehttpheaders.live.oHeaderInfoLive.onRATimeOut(" + i + ")", this.RATimeOut);
    },
    
    // desactivates the timeout, since the object received a response
    endRATimeOut: function(i){
        this.RAtimeouts[i] = 0;
    },
    
    // event that happens when some time has transcurred since the first object of a document was requested
    onRATimeOut: function(i){
        // If there is no more timouts planified
        if (this.RAtimeouts[i] && this.replayingAll) {
            // The ReplayAll proccess ended
            //alert("WARNING: the timeout of the only object planned has expired"); //***
            this.replayAllEnd();
        }
    },
    
    // Function inspired from DownThemAll Mozilla Firefox extension +++
    multipleReplace: function(filename, invalidchar, newchar){
        while (filename.indexOf(invalidchar) > -1) {
            filename = filename.replace(invalidchar, newchar);
        }
        return filename;
    },
    
    // Function inspired from DownThemAll Mozilla Firefox extension +++
    createNumber: function(number){
        var Stringa;
        if (number <= 9) {
            Stringa = "00" + number;
        }
        else 
            if (number <= 99 && number >= 10) {
                Stringa = "0" + number;
            }
            else {
                Stringa = number;
            }
        return Stringa;
    },
    
    // Function inspired from DownThemAll Mozilla Firefox extension +++
    toggleInvalidChar: function(filename){
        // remove from the filename invalid characters (in case of dinamic pages)
        filename = this.multipleReplace(filename, "?", "");
        filename = this.multipleReplace(filename, "&", "");
        filename = this.multipleReplace(filename, "=", "");
        filename = this.multipleReplace(filename, "<", "");
        filename = this.multipleReplace(filename, ">", "");
        filename = this.multipleReplace(filename, ":", "");
        filename = this.multipleReplace(filename, "%20", " ");
        return filename;
    },
    
    downloadFileReplayAll: function(url, referer){
        dir = "d:\\test2"; // Directory where downloads will automatically be saved when ReplayAll: directory.value;
        this.downloadFile(url, referer, dir);
    },
    
    // Function inspired from DownThemAll Mozilla Firefox extension +++
    downloadFile: function(url, referer, dir){
    
        //var url = downloadlist[y + completedownloads + dwlinProgr];
        
        // remove the last slash in url
        if (url.substring(url.length - 1, url.length) == "/") {
            url = url.substring(0, url.length - 1);
        }
        
        //find the url filename
        var filename = url.substring(url.lastIndexOf("/") + 1, url.length);
        
        filename = this.toggleInvalidChar(filename);
        
        // create the local file
        const FileFactory = new Components.Constructor("@mozilla.org/file/local;1", "nsILocalFile", "initWithPath");
        const nsIWBP = Components.interfaces.nsIWebBrowserPersist;
        var dest = new FileFactory(dir);
        
        // if the directory doesn't exist, create it
        if (!dest.exists()) 
            dest.create(dest.DIRECTORY_TYPE, 511);
        
        // prepend a number to filename
        // TODO: make this customizable with an option
        filename = this.createNumber(this.storeFilesCounter++) + "_" + filename;
        
        dest.append(filename);
        var i = 0;
        
        // if the file already exists, change the name
        while (dest.exists()) {
            i++;
            dest = null;
            dest = new FileFactory(dir);
            dest.append(this.createNumber(i) + "_" + filename);
        }
        
        // start the download
        dest.create(dest.NORMAL_FILE_TYPE, 511);
        var uri = Components.classes['@mozilla.org/network/standard-url;1'].createInstance(Components.interfaces.nsIURI);
        uri.spec = url;
        var persist = Components.classes['@mozilla.org/embedding/browser/nsWebBrowserPersist;1'].createInstance(Components.interfaces.nsIWebBrowserPersist);
        persist.persistFlags = nsIWBP.PERSIST_FLAGS_NO_CONVERSION | nsIWBP.PERSIST_FLAGS_REPLACE_EXISTING_FILES | nsIWBP.PERSIST_FLAGS_BYPASS_CACHE;
        var refPage = Components.classes['@mozilla.org/network/standard-url;1'].createInstance(Components.interfaces.nsIURI);
        refPage.spec = referer;
        persist.saveURI(uri, null, refPage, null, null, dest);
    },
    
    replayer: function(){
    
        var i = this.replayAllCounter;
        
        // For every document
        if (i < this.RAurls.length) {
        
            // TODO: Find a way to request objects that does not stop current object loads
            
            if (this.storefilesra && this.RAurls[i].match(this.storefilesdirraregexp)) {
                // download it
                var refstr = "\nReferer: ";
                var referer = this.RArequests[i];
                var referer = referer.substring((referer.indexOf(refstr) + refstr.length), referer.length);
                var referer = referer.substring(0, referer.indexOf("\n"));
                
                this.downloadFile(this.RAurls[i], referer, this.storefilesdirra); //+++
            }
            else {
                // request the file, 
                this.play(this.RAmethods[i], this.RAurls[i], this.RAversions[i], this.RArequests[i], this.RApostdatas[i]);
            }
            
            this.setLastAction("Replaying request " + (i + 1) + "/" + this.replayAllTotal + ": " + this.RAurls[i] + " Think Time: " + this.RAtimes[i]);
        }
    },
    
    // Replays all the objects in the LHH window
    replayAllStart: function(){
    
        // Make public the new condition of 'Replay All'
        this.disableButtonsReplayAll();
        this.replayingAll = 1;
        document.getElementById("headerstatusbar-status").value = "Replaying";
        document.getElementById(this.replayall).removeAttribute('disabled');
        document.getElementById(this.replayallNo).setAttribute('disabled', 'true');
        
        // For every object
        var objects = this.LHHXML.getElementsByTagName(this.XOBJECT);
        var j = 0;
        for (var i = 0; i < objects.length; i++) {
            // If the user requested it, then we should request it also
            if ((objects[i].getAttribute(this.XUSER_CLICKED) == 1 || !this.reqAsUserReplayAll) &&
            (objects[i].getElementsByTagName(this.XREQUEST).length > 0)) {
                // Copy all the important data to temporary variables
                this.RAurls[j] = objects[i].getAttribute(this.XURL);
                this.RAmethods[j] = objects[i].getAttribute(this.XMETHOD);
                this.RAversions[j] = objects[i].getAttribute(this.XVERSION);
                this.RAusercls[j] = objects[i].getAttribute(this.XUSER_CLICKED);
                
                // Request
                var requests = objects[i].getElementsByTagName(this.XREQUEST);
                var request_headers = requests[0].getElementsByTagName(this.XREQUEST_HEADER);
                this.RArequests[j] = "";
                for (var k = 0; k < request_headers.length; k++) 
                    this.RArequests[j] += request_headers[k].textContent + "\r\n";
                
                // Post
                var posts = requests[0].getElementsByTagName(this.XPOST);
                if (posts.length > 0) {
                    this.RApostdatas[j] = posts[0].textContent;
                    for (var k = 1; k < posts.length; k++) 
                        this.RApostdatas[j] += posts[k].textContent;
                }
                
                // Calculate waiting time until the next document
                if ((objects[i].parentNode.getAttribute(this.XDOC_THINKTIME) != null) ||
                (objects[i].parentNode.parentNode.getAttribute(this.XDOC_THINKTIME) != null)) {
                    var F = parseInt(objects[i].parentNode.getAttribute(this.XDOC_START)) +
                    parseInt(objects[i].parentNode.getAttribute(this.XDOC_LATENCY)) +
                    parseInt(objects[i].parentNode.getAttribute(this.XDOC_THINKTIME));
                    var a = objects[i].getAttribute(this.XRESPONSE_TIME);
                    var t = F - a;
                    // Think Time (applied when the ReplayAll and the first object of a document is received) ***
                    //  == timestamp when the following document starts
                    //      - timestamp when the response for the first requested object is received
                    //  == time that passed from reception of the first object of the document until
                    //      the next document is requested
                    this.RAtimes[j] = t;
                }
                
                j++;
            }
        }
		//For every document
        var documents = this.LHHXML.getElementsByTagName(this.XDOCUMENT);
		var k = 0;
        for (var i = 0; i < documents.length; i++) {
			var doc= documents[i];		
			//get the browser states structure
			this.RABrowserStates[i] = doc.getElementsByTagName(this.XBROWSER_STATES).item(0);
			k++;
		}
        
        // Free disk and memory cache
        if (this.clearCacheReplayAll) 
            this.clearCache();
        
        // Clear the dataXML variable, and initialize for the future use
        this.initXMLStream();
        
        // Clear the LHH window
        this.clear();
        
        //var browser = (window.opener == null)? window.getWebNavigation() : window.opener.getWebNavigation();
        //var windowMediator = Components.classes["@mozilla.org/appshell/window-mediator;1"] .getService(Components.interfaces.nsIWindowMediator); 
        //browser = windowMediator.getMostRecentWindow("navigator:browser").getBrowser();
        
        this.replayAllCounter = 0;
        this.replayAllTotal = j;
		this.currentDocument = 0;
		this.nbDocuments = k;
        
        // Request the first page now
        this.replayer();
    },
    
    // Function to select a file to import
    selectFile: function(data, title){
        if (!title) 
            title = "Import an eCarena *.XML capture file";
        const kFilePickerCID = "@mozilla.org/filepicker;1";
        const nsIFilePicker = Components.interfaces.nsIFilePicker;
        
        try {
            var fpicker = Components.classes[kFilePickerCID].createInstance();
            var fp = fpicker.QueryInterface(nsIFilePicker);
            if (!fp) 
                return false;
        } 
        catch (ex) {
            alert("Error: Dialog window could not be loaded.\n" + ex);
            return false;
        }
        
        fp.init(window, title, nsIFilePicker.modeOpen);
        fp.appendFilter("eCARENA rich XML file", "*.xml");
        fp.appendFilters(nsIFilePicker.filterAll); // All the files - *.*
        var result = fp.show();
        
        if (result == nsIFilePicker.returnOK) {
            var nombre_arch = fp.file;
            return nombre_arch;
        }
    },
    
    // A new object request has been sent, now we'll store the data
    incrNumObj: function(doc, obj){
        if (obj.getAttribute(this.XPREFETCH) != "true") 
            doc.setAttribute(this.XNUMOBJECTS, parseInt(doc.getAttribute(this.XNUMOBJECTS)) + 1);
    },
    
    // A new object request has been sent, now we'll store the data
    objStartLoad: function(aSubject, aTopic, aData){
    
        // Create a new Object
        var newObject = this.dataXML.createElement(this.XOBJECT);
        
        var myurl = aSubject.URI.asciiSpec;
        if (aSubject.referrer != null) 
            var myreferrer = aSubject.referrer.asciiSpec;
        
        // Store some attributes
        newObject.setAttribute(this.XURL, myurl);
        
        var time = new Date();
        newObject.setAttribute(this.XREQUEST_TIME, time.getTime());
        
        if (aSubject.referrer != null) 
            newObject.setAttribute(this.XREFERRER, myreferrer);
        
        newObject.setAttribute(this.XSTATUS, "");
        
        // Set's XPREFETCH if this object is a prefetch
        // Get the request headers
        var visitor = new livehttpheaders.live.HeaderInfoVisitor(aSubject);
        var request = visitor.visitRequest();
        
        // If this request is a prefetch
        if (request["X-Moz"] == "prefetch") 
            newObject.setAttribute(this.XPREFETCH, "true");
        
        // Check if this object was requested by an
        // user triggered action: hyperlink, form, URL bar, bookmark, 
        //   Reload, Back, Forward (3xx redirects)
        if (aSubject.loadFlags & aSubject.LOAD_INITIAL_DOCUMENT_URI) 
            var userClicked = 1;
        else 
            var userClicked = 0;
        newObject.setAttribute(this.XUSER_CLICKED, userClicked);
        
        // Check if this object is the first in a frame (65536)
        newObject.setAttribute("loadFlags", aSubject.loadFlags);
        if ((aSubject.loadFlags - 65536) == 0) 
            var isFirstInFrame = 1;
        else 
            var isFirstInFrame = 0;
        
        // Get documents and frames
        var documents = this.LHHXML.getElementsByTagName(this.XDOCUMENT);
        var frames = this.LHHXML.getElementsByTagName(this.XFRAME);
        
        // Object from a new document, still inexistent
        if (userClicked) {
            // Check if it was called from a frame
            var i = this.findNode(myreferrer, this.XFRAME, 1);
            var j = this.findNode(myreferrer, this.XDOCUMENT, 1);
            
            //alert("i: " + i + "; j: " + j);
            if ((i != -1) ||
            ((j != -1) && (documents[j].getAttribute(this.XDOC_FROMFRAME) != null))) { //Tiene problemas
                //if ( (i != -1) || (documents[j].getAttribute(this.XDOC_FROMFRAME) != null) ) { //Tiene problemas
                // If the object was called from a frame
                // we must create manually the parent document, since no events will happen
                this.docStartLoad(myurl, 1);
                
                var k = this.findNode(myurl, this.XDOCUMENT, 1);
                //alert("i: " + i + "; j: " + j + "; k: " + k);
                var node = documents[k];
                node.setAttribute(this.XDOC_FROMFRAME, myreferrer);
            }
            else {
                var node = this.LHHXML;
            }
        }
        
        // Embedded object, the first in a frame
        if (!userClicked && isFirstInFrame) {
            // This is the first object of a frame
            // Create a new Frame structure
            var frame = this.dataXML.createElement(this.XFRAME);
            frame.setAttribute(this.XURL, myurl);
            
            // Search the Frame's parent
            var documents = this.LHHXML.getElementsByTagName(this.XDOCUMENT);
            var frames = this.LHHXML.getElementsByTagName(this.XFRAME);
            var i = this.findNode(myreferrer, this.XDOCUMENT, 1);
            var j = this.findNode(myreferrer, this.XFRAME, 1);
            
            // Check if we found a parent document; if not, check for a parent frame
            if (i != -1) 
                parent = documents[i];
            else 
                if (j != -1) 
                    parent = frames[j];
                else {
                    alert("ERROR: an object was requested, but the referer was not requested!");
                    return -1;
                }
            
            // Append the new frame to the parent
            parent.appendChild(frame);
            
            var node = frame;
            
            // Initialize the object parent counter
            node.setAttribute(this.XNUMOBJECTS, 0);
            //node.setAttribute(this.XNUMNONRESPONSES, 0);
            
            // Increment the object parent counter
            this.incrNumObj(parent, newObject);
        }
        
        // Embedded object that is not the first in a frame
        if (!userClicked && !isFirstInFrame) {
            // doc o frame exists: find it and attach to it
            var a = this.findNode(myreferrer, this.XDOCUMENT, 1);
            var b = this.findNode(myreferrer, this.XFRAME, 1);
            
            // Document exists
            if (a != -1) {
                // Frame exists
                if (b != -1) {
                    // the document is the frame parent
                    if (frames[b].parentNode == documents[a]) {
                        var node = frames[b];
                    }
                    else {
                        var node = documents[a];
                    }
                }
                // Frame does NOT exist
                else {
                    // object from a document without frames
                    var node = documents[a];
                }
            }
            // Document does NOT exist
            else {
                if (b != -1) {
                    var node = frames[b];
                }
                else {
                    // The LHH window was cleared but the browser window still contains frames
                    var node = this.LHHXML;
                }
            }
        }
        
        // Check if this object is the result of a redirection
        // The URL of this object should be the url-redirect of a document
        var a = this.findRedirector(myurl);
        if (a != -1) {
            var node = documents[a];
        }
        
        // Finally append this object to the corresponding node
        node.appendChild(newObject);
        document.getElementById("headerstatusbar-numobjs").value = parseInt(document.getElementById("headerstatusbar-numobjs").value) + 1;
        
        // Increment the object parent counter
        if (node != this.LHHXML) 
            this.incrNumObj(node, newObject);
        //node.setAttribute(this.XNUMNONRESPONSES,
        //  parseInt(node.getAttribute(this.XNUMNONRESPONSES)) + 1);
    },
    
    // A response has been received
    objEndLoad: function(aSubject, aTopic, aData){
    
        // Find and get the object
        var objects = this.LHHXML.getElementsByTagName(this.XOBJECT);
        newObject = objects[this.findNode(aSubject.URI.asciiSpec, this.XOBJECT, 0)];
        
        // Store some attributes
        var time = new Date();
        newObject.setAttribute(this.XRESPONSE_TIME, time.getTime());
        newObject.setAttribute(this.XLATENCY, (newObject.getAttribute(this.XRESPONSE_TIME) -
        newObject.getAttribute(this.XREQUEST_TIME)));
        
        // Get the parent URL
        var docurl = String();
        // If the user clicked to get this object
        if (newObject.getAttribute(this.XUSER_CLICKED) == 1) 
            // Then the URL of his parent document is this object URL
            docurl = newObject.getAttribute(this.XURL);
        else 
            docurl = newObject.getAttribute(this.XREFERRER);
        
        // Find and get the document
        var documents = this.LHHXML.getElementsByTagName(this.XDOCUMENT);
        var i = this.findNode(docurl, this.XDOCUMENT, 0);
        
        // If this response was a Redirect HTTP Response
        var urlRedirect = newObject.getAttribute(this.XURL_REDIRECT);
        if (urlRedirect != null) 
            // and this object was requested by the user
            if (newObject.getAttribute(this.XUSER_CLICKED) == 1) 
                // then all the document will be redirected
                documents[i].setAttribute(this.XURL_REDIRECT, urlRedirect);
        //else
        // else only this object is redirected
        //newObject.setAttribute(this.XURL_REDIRECT, urlRedirect);
        
        // FRAME THING: We must manually update the document, since no events will happen
        //if (i == -1) { // If the url of the referrer does not correspond to a document
        // It'll be of frame
        //}
        
        // Get the parent document of the frame
        var node = newObject.parentNode;
        
        while ((node.localName != this.XDOCUMENT) && (node.localName != this.XCARENA)) 
            node = node.parentNode;
        
        var docurl2 = node.getAttribute(this.XURL);
        
        // If this is not a prefetch object
        if (newObject.getAttribute(this.XPREFETCH) != "true") 
            // update the information for the parent document (calling docEndLoad)
            this.docEndLoad("", docurl2);
        
        // If we are replaying all AND If this is the first object in a document 
        if ((this.replayingAll > 0) && (newObject.getAttribute(this.XUSER_CLICKED) == 1)) {
        
            // Desactivates de document timeout
            this.endRATimeOut(i);
            
            // If it's not the last
            if (this.RAtimes[this.replayAllCounter] != null) {
                // planify the next object
                var time = this.RAtimes[this.replayAllCounter];
                setTimeout("livehttpheaders.live.oHeaderInfoLive.replayer()", time);
                //alert("Se planifica para dentro de " + time + "ms. la peticion num " + this.replayAllCounter); //***
                this.replayAllCounter++;
            }
            else {
                // The ReplayAll proccess ended
                this.replayAllEnd();
            }
        }
        
        // If this object does not have parent yet
        if (newObject.parentNode.localName == this.XCARENA) {
            // Find the last document
            lastDocument = documents[documents.length - 1];
            // and it will be his father
            lastDocument.appendChild(newObject);
        }
        
        //node = documents[i];
        //node.setAttribute(this.XNUMNONRESPONSES,
        //  parseInt(node.getAttribute(this.XNUMNONRESPONSES)) - 1);
    },
    
    // Remove the tailing #section from the url
    removeTailURL: function(url){
    
        if (url != null) {
            url2c = url.indexOf('#');
            if (url2c != -1) 
                url = url.substring(0, url.indexOf('#'));
        }
        return url;
    },
    
    // A document load has started
    docStartLoad: function(myurl, isFrame){
    
        time = new Date();
        
        // Get all the documents stored on LHH
        var documents = this.LHHXML.getElementsByTagName(this.XDOCUMENT);
		
		//we save a screenshot of the document if it's the first time
		if (this.findNode(myurl, this.XDOCUMENT, 1) == -1) 
		{
			sg.Grab(new sg.FrameTarget(), sg.CaptureViewPort, new sg.SaveAction());
		}
        
        // Create a new document structure
        var newDocument = this.dataXML.createElement(this.XDOCUMENT);

        // Add attributes
        newDocument.setAttribute(this.XURL, myurl);
        newDocument.setAttribute(this.XNUMOBJECTS, 0);
        //newDocument.setAttribute(this.XNUMNONRESPONSES, 0);
        newDocument.setAttribute(this.XDOC_START, time.getTime());
		
		        
		//Create a browserStates structure for the new document
		var newBrowserStates = this.dataXML.createElement(this.XBROWSER_STATES);
		
		//Add attributes
		newBrowserStates.setAttribute(this.XNUMSTATES,0);
		        
        // Check if exists documents before the new one
        if (documents.length > 0) {
            var lastdoc = documents[documents.length - 1];
            
            // Add Thinking time atribute to previous document
            // TODO: improve think time calculation
            lastdoc.setAttribute(this.XDOC_THINKTIME, time.getTime() - lastdoc.getAttribute(this.XDOC_END));
            
            // TO-DO FRAME
            // If the last document was generated manually by us
            // because its referer is a frame (docFromFrame & docThinkTime>1000000000000
            if ((lastdoc.getAttribute(this.XDOC_FROMFRAME) != "null") &&
            (lastdoc.getAttribute(this.XDOC_THINKTIME) > 100000000000)) {
                // The last document should have not been manually created: remove it
                newDocument.setAttribute(this.XDOC_FROMFRAME, lastdoc.getAttribute(this.XDOC_FROMFRAME));
                lastdoc.setAttribute(this.XDOC_REMOVEME, "1");
            }
			
			//we stop the previous timer
			clearInterval(this.browserStateCaptureTimer);
        }
        
        // Append the new document to LHHXML
        this.LHHXML.appendChild(newDocument);
        document.getElementById("headerstatusbar-numdocs").value = parseInt(document.getElementById("headerstatusbar-numdocs").value) + 1;
        
		// Append the browserStates to the document
		newDocument.appendChild(newBrowserStates);
		
        // If this is not a frame
        if (!isFrame) {
            // Move the first object into this document
            var objects = this.LHHXML.getElementsByTagName(this.XOBJECT);
            // Search an object with url= doc.url from the end
            newChild = objects[this.findNode(myurl, this.XOBJECT, 0)];
            newDocument.appendChild(newChild);
            
            // Increment the document atribute that counts the number of objects
            this.incrNumObj(newDocument, newChild);
            //newDocument.setAttribute(this.XNUMNONRESPONSES,
            //  parseInt(newDocument.getAttribute(this.XNUMNONRESPONSES)) + 1);
            
            // If we are replaying all
            if (this.replayingAll) {
                // Planificate a timeout, just in case we do not receive the first response
                this.startRATimeOut(documents.length - 1);
            }
        }
		
		//sg.Grab(new sg.FrameTarget(), sg.CaptureViewPort, new sg.SaveAction());
		
		//we start the timer to regularly save the browser state
		var delay = livehttpheaders.live.oHeaderInfoLive.browserStateCaptureInterval;
		this.browserStateCaptureTimer = setInterval(livehttpheaders.live.oHeaderInfoLive.saveBrowserState,delay);
    },
    
    // Find a node, given its URL and type
    findElement: function(url, element_type, tailed){
    
        var nodes = this.LHHXML.getElementsByTagName(element_type);
        
        // Start cheking nodes by the end
        for (var i = nodes.length - 1; i >= 0; i--) {
            nodeUrl = nodes[i].getAttribute(this.XURL);
            nodeUrlRedirect = nodes[i].getAttribute(this.XURL_REDIRECT);
            if (tailed) 
                nodeUrl = this.removeTailURL(nodeUrl);
            nodeUrlRedirect = this.removeTailURL(nodeUrlRedirect);
            
            // If the urls are the same, and it is still loading
            if (nodeUrl == url || nodeUrlRedirect == url) 
                // && nodes[i].getAttribute(this.XDOC_END)==null)
                // Then we found the document
                return i;
        }
        
        // If we reach this point, we did not find the document
        return -1;
    },
    
    // Find a node, given its URL and type
    findNode: function(url, type, tailed){
        return this.findElement(url, type, tailed)
    },
    
    // Find redirector, if available
    findRedirector: function(url){
    
        var nodes = this.LHHXML.getElementsByTagName(this.XDOCUMENT);
        
        // Start cheking nodes by the end
        for (var i = nodes.length - 1; i >= 0; i--) {
            nodeUrlRedirect = nodes[i].getAttribute(this.XURL_REDIRECT);
            nodeUrlRedirect = this.removeTailURL(nodeUrlRedirect);
            
            // If the urls are the same, and it is still loading
            if (nodeUrlRedirect == url) 
                // && nodes[i].getAttribute(this.XDOC_END)==null)
                // Then we found the document
                return i;
        }
        
        // If we reach this point, we did not find the document
        return -1;
    },
    
    // A document load has ended
    docEndLoad: function(mystatus, myurl){
    
        var documents = this.LHHXML.getElementsByTagName(this.XDOCUMENT);
        
        // Find the correct document
        var i = this.findNode(myurl, this.XDOCUMENT, 0);        
        
        if (i == -1) {
            //alert ("[eCarena] This document ends but cannot be found: " + myurl + "\r\nStatus: " + mystatus);
            return;
        }
        
        var document = documents[i];
        
        // Add atributes to it
        time = new Date();
        document.setAttribute(this.XDOC_END, time.getTime());
        
        document.setAttribute(this.XDOC_LATENCY, (document.getAttribute(this.XDOC_END) -
        document.getAttribute(this.XDOC_START)));
        
        document.setAttribute(this.XDOC_STATUS, mystatus);
        
        // Propagate the document status to all its child objects that do not have status
        for (var j = 0; j < document.childNodes.length; j++) 
            if (document.childNodes[j].getAttribute(this.XSTATUS) == "") 
                document.childNodes[j].setAttribute(this.XSTATUS, mystatus);
        
        // If this is the first object in a document C045
        //if (newObject.getAttribute(this.XUSER_CLICKED) == 1)
        // Set the document Referrer
        //node.setAttribute(this.XDOC_REFERRER, newObject.getAttribute(this.XREFERRER));
        
        // If the document has at least one child
        if (document.childNodes.length > 0) 
            // If the document's first child does not have referrer
            if (document.childNodes[0].getAttribute(this.XREFERRER) == null) 
                // then we think that the referrer of the current document
                // is the url of the previous document (in time)
                if (i > 0) 
                    // If this is not the first document, copy the URL of the previous document
                    document.setAttribute(this.XREFERRER, documents[i - 1].getAttribute(this.XURL));
                else 
                    // If this is the first document, report unknown5
                    document.setAttribute(this.XREFERRER, "unknown5");
            else 
                // else copy the first object referrer as this document refererrer
                document.setAttribute(this.XREFERRER, document.childNodes[0].getAttribute(this.XREFERRER));
        else 
            // the document does not have any child
            document.setAttribute(this.XREFERRER, "unknown3");
        
        document.setAttribute(this.XDOC_REFERRER, "unknown1");
        
        // If docFromFrame is null
        // that is, this document was NOT linked from a frame
        if (document.getAttribute(this.XDOC_FROMFRAME) == null) 
            // then docReferrer is the same as 'referrer'
            var myDocReferrer = document.getAttribute(this.XREFERRER);
        else {
            // but if the referrer is a frame, then search that frame 
            var myreferrer = document.getAttribute(this.XREFERRER);
            var frames = this.LHHXML.getElementsByTagName(this.XFRAME);
            var i = this.findNode(myreferrer, this.XFRAME, 1);
            var j = this.findNode(myreferrer, this.XDOCUMENT, 1);
            
            if (i != -1) 
                // If the referred frame was found
                // then copy his referrer as the docReferrer
                var myDocReferrer = frames[i].parentNode.getAttribute(this.XURL);
            else 
                // but if the referred frame was not found
                // then check if the referrer was a document
                if (j != -1) 
                    // If the referrer was a document
                    // then copy his URL as the docReferrer
                    var myDocReferrer = documents[j].getAttribute(this.XURL);
                else 
                    var myDocReferrer = "unknown2";
        }
        document.setAttribute(this.XDOC_REFERRER, myDocReferrer);
		
		//we start replaying browser states
		if(this.replayingAll && this.currentDocument < this.nbDocuments)
		{
			this.currentBrowserState = 0;
			this.currentDocument++;
			this.replayBrowserState();
		
		}
			
    },
    
    // Import a file in XML format into LHH structures
    importFile: function(){
    
        // Show file selection dialog
        var fileI = this.selectFile(this.getAll(), "Import an eCarena *.XML capture file");
        
        // If no file was selected, exit
        if (fileI == null) 
            return;
        
        // Clear Window and Session
        this.clearSession();
        
        // old code to load the xml capture file : NOT WORKING :( 
        /*var doc = document.implementation.createDocument("", "doc", null);
        doc.load("file:///" + fileI.path); // cause a security exception, we don't know why ???
		*/
		
      // new code to load the xml capture file
	  var doc = null;
      try 
      {
         var ioservice = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
         var file = "file:///"+fileI.path; 
         var channel = ioservice.newChannel(file,null,null);
         var stream = channel.open();
         //alert("stream.available()=" + stream.available()); // debug
         var parser = new DOMParser();
         doc = parser.parseFromStream(stream, "", stream.available(),"text/xml");
         //alert("doc="+doc); // debug
         stream.close(); 
      }   
      catch (e)
      {
         alert(e);
      }

      
        // Parse the XML structure 
        this.dataXML = doc;
        this.LHHXML = doc.getElementsByTagName(this.XCARENA).item(0);
        var objects = this.LHHXML.getElementsByTagName(this.XOBJECT);
        
        // Backup the rowcount
        var oldrows = this.rowCount;
        
        // For every object
        for (var i = 0; i < objects.length; i++) {
            // URL
            this.addRow(objects[i].getAttribute(this.XURL) + "\r\n", this.URL);
            this.addRow("\r\n", this.REQSPACE);
            
            // Request
            var requests = objects[i].getElementsByTagName(this.XREQUEST);
            //this.addRow(objects[i].getAttribute(this.XREQUEST_TIME) + ":\r\n", this.REQSPACE); // #DATAPLUS
            
            if (requests.length > 0) {
                var request_headers = requests[0].getElementsByTagName(this.XREQUEST_HEADER);
                for (var j = 0; j < request_headers.length; j++) 
                    this.addRow(request_headers[j].textContent + "\r\n", this.REQUEST);
                
                // Post
                var posts = requests[0].getElementsByTagName(this.XPOST);
                for (var j = 0; j < posts.length; j++) 
                    this.addRow(posts[j].textContent + "\r\n", this.POSTDATA);
                this.addRow("\r\n", this.RESSPACE);
                
                // Response
                //this.addRow(objects[i].getAttribute(this.XRESPONSE_TIME) + ":\r\n", this.RESSPACE); // #DATAPLUS
                var responses = objects[i].getElementsByTagName(this.XRESPONSE);
                var response_headers = responses[0].getElementsByTagName(this.XRESPONSE_HEADER);
                for (var j = 0; j < response_headers.length; j++) 
                    this.addRow(response_headers[j].textContent + "\r\n", this.RESPONSE);
                
            }
            // Separator
            this.addRow("", this.SEPARATOR);
        }
        
        this.rowCountChanged(oldrows, (this.rows - oldrows));
        this.setLastAction("Imported " + i + " objects from file: " + fileI.path);
        document.getElementById("headerstatusbar-numobjs").value = i;
    },
    
    setStoreFilesra: function(storefilesra){
        this.setBoolPref(this.epref, "storefilesra", storefilesra);
        this.storefilesra = storefilesra;
    },
    setStoreFilesDirra: function(storefilesdirra){
        this.setCharPref(this.epref, "storefilesdirra", storefilesdirra);
        this.storefilesdirra = storefilesdirra;
    },
    setStoreFilesDirraregexp: function(storefilesdirraregexp){
        this.setCharPref(this.epref, "storefilesdirraregexp", storefilesdirraregexp);
        this.storefilesdirraregexp = storefilesdirraregexp;
    },
    setsavexml: function(savexml){
        this.setBoolPref(this.epref, "savexml", savexml);
        this.savexml = savexml;
    },
	setBrowseStateCaptureInterval:function(interval)
	{
		this.setIntPref(this.epref, "browserStateCaptureInterval", interval);
		this.browserStateCaptureInterval = interval;
	},
    setnotClearUrlInfo: function(notClearUrlInfo){
        this.setBoolPref(this.epref, "notClearUrlInfo", notClearUrlInfo);
        this.notClearUrlInfo = notClearUrlInfo;
    },
    setclearCacheReplayAll: function(clearCacheReplayAll){
        this.setBoolPref(this.epref, "clearCacheReplayAll", clearCacheReplayAll);
        this.clearCacheReplayAll = clearCacheReplayAll;
    },
    setreqAsUserReplayAll: function(reqAsUserReplayAll){
        this.setBoolPref(this.epref, "reqAsUserReplayAll", reqAsUserReplayAll);
        this.reqAsUserReplayAll = reqAsUserReplayAll;
    },
    settemporizePerUserReplayAll: function(temporizePerUserReplayAll){
        this.setBoolPref(this.epref, "temporizePerUserReplayAll", temporizePerUserReplayAll);
        this.temporizePerUserReplayAll = temporizePerUserReplayAll;
    },
    
    setTab: function(tab){
        this.setBoolPref(this.epref, "tab", tab);
    },
    setFilter: function(filter){
        this.setBoolPref(this.epref, "filter", filter);
        this.usefilter = filter;
    },
    setFilterRegexp: function(regex){
        this.setCharPref(this.epref, "filterRegexp", regex);
        this.filterRegexp = regex;
    },
    setExclude: function(exclude){
        this.setBoolPref(this.epref, "exclude", exclude);
        this.useexclude = exclude;
    },
    setExcludeRegexp: function(regex){
        this.setCharPref(this.epref, "excludeRegexp", regex);
        this.excludeRegexp = regex;
    },
    setStyle: function(style){
        if (style) {
            style = 1;
        }
        else {
            style = 0;
        }
        this.setIntPref(this.epref, "style", style);
        this.usestyle = style;
    },
    setMode: function(mode){
        this.setIntPref(this.epref, "mode", mode);
        this.mode = mode;
    },
    observeURL: function(name, request, response, postData){
        //dumpall("REQUEST",request);
        if (this.isCapturing) {
            // Find and get the object
            var objects = this.LHHXML.getElementsByTagName(this.XOBJECT);
            newObject = objects[this.findNode(name, this.XOBJECT, 0)];
            if (this.usefilter && !name.match(this.filterRegexp)) 
                return;
            if (this.useexclude && name.match(this.excludeRegexp)) 
                return;
            var oldrows = this.rowCount;
            newObject.setAttribute(this.XURL, name);
            this.addRow(name + "\r\n", this.URL);
            this.addRow("\r\n", this.REQSPACE);
            var flag = false;
            requestL = new String();
            var newRequest = this.dataXML.createElement(this.XREQUEST);
            var method = false;
            for (i in request) {
                if (!method) {
                    var tmp = request[i].match(/^(\S+).*\/(\S+)/);
                    newObject.setAttribute(this.XMETHOD, tmp[1]);
                    newObject.setAttribute(this.XVERSION, tmp[2]);
                    method = true;
                }
                this.addRow((flag ? i + ": " : "") + request[i] + "\r\n", this.REQUEST);
                var newHeader = this.dataXML.createElement(this.XREQUEST_HEADER);
                var newHeaderText = this.dataXML.createTextNode((flag ? i + ": " : "") + request[i]);
                newHeader.appendChild(newHeaderText);
                newRequest.appendChild(newHeader);
                requestL = requestL + (flag ? i + ": " : "") + request[i] + "\r\n";
                flag = true;
            }
            // var rawData = null; //+++ added on LHH 0.12
            if (postData) {
                var data = postData.match(/^.*(\r\n|\r|\n)?/mg); // "\r\n"
                for (i in data) {
                    this.addRow(data[i], this.POSTDATA);
                    var newHeader = this.dataXML.createElement(this.XPOST);
                    var newHeaderText = this.dataXML.createTextNode(data[i]);
                    newHeader.appendChild(newHeaderText);
                    newRequest.appendChild(newHeader);
                }
            }
            this.addRow("\r\n", this.RESSPACE);
            flag = false;
            responseL = new String();
            var newResponse = this.dataXML.createElement(this.XRESPONSE);
            for (i in response) {
                // Server can send some headers multiple times...
                // Try to detect this and present them in the 'good' way.
                var multi = response[i].split('\n');
                for (var o in multi) {
                    this.addRow((flag ? i + ": " : "") + multi[o] + "\r\n", this.RESPONSE);
                    var newHeader = this.dataXML.createElement(this.XRESPONSE_HEADER);
                    var newHeaderText = this.dataXML.createTextNode((flag ? i + ": " : "") + multi[o]);
                    newHeader.appendChild(newHeaderText);
                    newResponse.appendChild(newHeader);
                    responseL = responseL + (flag ? i + ": " : "") + multi[o] + "\r\n";
                }
                flag = true;
            }
            newObject.setAttribute(this.XOBJECT_SIZE, response["Content-Length"]);
            newObject.setAttribute(this.XREFERRER, request["Referer"]);
            newObject.setAttribute(this.XSTATUS, response["RESPONSE"]); // TODO: find a better way to store this info
            newRequest.setAttribute(this.XREQUEST_LENGHT, requestL.length);
            newResponse.setAttribute(this.XRESPONSE_LENGHT, responseL.length);
            
            // If this response was a Redirect HTTP Response (3xx)
            if (response["RESPONSE"].indexOf('3', 8) == 9) 
                newObject.setAttribute(this.XURL_REDIRECT, response["Location"]);
            this.addRow("", this.SEPARATOR); // Separator
            this.rowCountChanged(oldrows, (this.rows - oldrows));
            newObject.appendChild(newRequest);
            newObject.appendChild(newResponse);
            
            // Get all the documents
            var documents = this.LHHXML.getElementsByTagName(this.XDOCUMENT);
            
            // Get the parent document URL
            // Find the correct document
            var docurl = String();
            if (newObject.getAttribute(this.XUSER_CLICKED) == 1) 
                // If this object is the main one
                var i = this.findNode(name, this.XDOCUMENT, 0)
            else 
                // if not, the url is stored on the Referer 
                var i = this.findNode(request["Referer"], this.XDOCUMENT, 0)
        }
    },
    
    onModifyRequest: function(oHttp){
        //dump("onModifyRequest\n");
        //dumpall("Request", oHttp,2);
        //dump("MODIFY: '" + oHttp.URI.asciiSpec +"'\n");
        
        //this.onExamineResponse(oHttp);
        //alert("REquest: " + oHttp)
        //this.test[oHttp.URI.asciiSpec] = 1
        
        if (oHttp.URI.asciiSpec in this.check) {
            //dumpall("Request", oHttp,1);
            // This observer is designed to delete all observed headers
            function emptyObserver(oHttp){
                this.oHttp = oHttp;
                this.request = new Array();
            }
            emptyObserver.prototype = {
                visitHeader: function(name, value){
                    this.request[name.toLowerCase()] = value;
                },
                emptyHeaders: function(){
                    oHttp.visitRequestHeaders(this);
                    for (var i in this.request) {
                        this.oHttp.setRequestHeader(i, null, false);
                    }
                }
            }
            var empty = new emptyObserver(oHttp);
            empty.emptyHeaders();
            
            //Get the URI and request array
            //dump("BINGO: " + uri);
            var uri = oHttp.URI.asciiSpec;
            var req = this.check[uri];
            delete this.check[uri];
            
            //Set the new headers
            oHttp.requestMethod = req['_METHOD_']
            delete req['_METHOD_']
            for (var i in req) {
                try {
                    //dump("Try: " + i + " = " + req[i] + "\n");
                    //if (i == 'Content-Type' || i == 'Content-Length') {
                    //  oHttp.setRequestHeader(i, null, false);
                    //} else {
                    oHttp.setRequestHeader(i, req[i], false);
                    //}
                } 
                catch (ex) {
                    //dump("onModifyRequest: exception: " + ex +"\n");
                }
            }
            //oHttp.requestMethod = "Get";
            //oHttp.QueryInterface(Components.interfaces.nsICachingChannel);
            //oHttp.cacheToken = null;
            //oHttp.cacheKey = null;
            //oHttp.loadFlags = oHttp.LOAD_NORMAL;
            //oHttp.loadFlags = oHttp.LOAD_BYPASS_CACHE;
            //oHttp.loadFlags = oHttp.VALIDATE_ALWAYS;
        }
        var uri = oHttp.URI.asciiSpec;
        for (observer in this.observers) {
            if ('observeGRequest' in this.observers[observer]) {
                this.observers[observer].observeGRequest(uri, oHttp.requestMethod)
            }
        }
    },
    
    onExamineResponse: function(oHttp){
        var name = oHttp.URI.asciiSpec;
        var origname = oHttp.originalURI.asciiSpec;
        var visitor = new livehttpheaders.live.HeaderInfoVisitor(oHttp);
        //dumpall("oHttp",oHttp,2);
        //alert("REsponse: " + oHttp)
        //alert(this.test[oHttp])
        //if (name in this.test) {
        //    dump("URL FOUND: " + name +"\n");
        //    delete this.test[name];
        //}
        //for (i in this.test) {
        //    dump("URL: " + i + "\n");
        //}
        
        // Get the request headers
        var request = visitor.visitRequest();
        // and extract Post Data if present
        var postData = request["POSTDATA"];
        delete request["POSTDATA"];
        //DSMT
        //this.request[name]["CACHE"] = oHttp.cacheToken;
        
        // Get the response headers
        var response = visitor.visitResponse();
        //dumpall("oHttp",oHttp,5);
        //dumpall("oHttp.loadGroup",oHttp.loadGroup,2);
        //dumpall("oHttp.loadGroup.groupObserver",oHttp.loadGroup.groupObserver,2);
        //dumpall("oHttp.loadGroup.groupObserver.DOMWindow",oHttp.loadGroup.groupObserver.DOMWindow,2);
        //dumpall("oHttp.loadGroup.groupObserver.container",oHttp.loadGroup.groupObserver.container,2);
        //dumpall("oHttp.loadGroup",oHttp.loadGroup,2);
        //dumpall("oHttp.loadGroup.groupObserver",oHttp.loadGroup.groupObserver,2);
        //dumpall("oHttp.loadGroup.notificationCallbacks",oHttp.loadGroup.notificationCallbacks,2);
        
        var rawData = null;
        if (postData && (this.isCapturing || this.observers.length > 0)) {
            this.t = postData.seekablestream; //DSMT: REPLAY
            //this.c = request["CACHE"];
            var size, mode;
            switch (this.mode) {
                case 0:
                    mode = 0;
                    size = 0;
                    break; //Don't get any content
                case 1:
                    mode = 1;
                    size = -1;
                    break; //Get content the fast way
                case 2:
                    mode = 2;
                    size = -1;
                    break; //Get content the accurate way
                case 3:
                    mode = 2;
                    size = 1024;
                    break; //Only get 1024 bytes of content
            }
            postData.setMode(mode);
            rawData = postData.getPostBody(size);
        }
        this.observeURL(name, request, response, rawData);
        
        // Call response observers
        var isRedirect = (name != origname)
        for (observer in this.observers) {
            //dumpall("Observer", this.observers[observer], 1);
            this.observers[observer].observeResponse(name, request, response, rawData, isRedirect)
        }
        
        //dumpall("Request",this.request[oHttp.name],1);
        //dumpall("Response",this.response[oHttp.name],1);
    },
    
    QueryInterface: function(iid){
        if (!iid.equals(Components.interfaces.nsISupports) &&
        !iid.equals(Components.interfaces.nsIHttpNotify) &&
        //!iid.equals(Components.interfaces.nsIClassInfo) &&
        //!iid.equals(Components.interfaces.nsISecurityCheckedComponent) &&
        //!iid.equals(Components.interfaces.nsIWeakReference) &&
        !iid.equals(Components.interfaces.nsIHttpNotify) &&
        !iid.equals(Components.interfaces.nsIObserver)) {
            //dump("LiveHTTPHeaders: QI unknown iid: " + iid + "\n");
            throw Components.results.NS_ERROR_NO_INTERFACE;
        }
        return this;
    },
	
	//Save the curent browser state
	saveBrowserState: function(){
		
		//global "this" pointer to access this class from the "outside"
		//as the function is called in a timer function (setInterval)
		var _this = livehttpheaders.live.oHeaderInfoLive;

		//get the last document
        var documents = _this.LHHXML.getElementsByTagName(_this.XDOCUMENT);
		if(documents.length == 0) return;		
        var lastDoc = documents[documents.length -1];
		
		//get the browser states structure
		var browserStates = lastDoc.getElementsByTagName(_this.XBROWSER_STATES).item(0);

		browserStates.setAttribute(_this.XNUMSTATES,parseInt(browserStates.getAttribute(_this.XNUMSTATES)) + 1);
		
		//create a new brower state
		var newBrowserState = _this.dataXML.createElement(_this.XBROWSER_STATE);
		newBrowserState.setAttribute(_this.XINTERVAL,_this.browserStateCaptureInterval);
		
		//mouse state
		var mouseState = _this.dataXML.createElement(_this.XMOUSE);
		mouseState.setAttribute(_this.XPOSITION_X,mouseX);
		mouseState.setAttribute(_this.XPOSITION_Y,mouseY);
		
		//window state
		var windowState = _this.dataXML.createElement(_this.XWINDOW);		
		windowState.setAttribute(_this.XPOSITION_X,getWindowX());
		windowState.setAttribute(_this.XPOSITION_Y,getWindowY());
		windowState.setAttribute(_this.XWIDTH, getClientWidth());
		windowState.setAttribute(_this.XHEIGHT, getClientHeight());
		
		
		//scrollbar state
		var scrollbarState = _this.dataXML.createElement(_this.XSCROLLBAR);
		scrollbarState.setAttribute(_this.XLEFT, getScrollLeft());
		scrollbarState.setAttribute(_this.XTOP, getScrollTop());
		
		// we append those states to the browserState
		newBrowserState.appendChild(mouseState);
		newBrowserState.appendChild(windowState);
		newBrowserState.appendChild(scrollbarState);
		
		//we append the new browserState to the browser states
		browserStates.appendChild(newBrowserState);
		
	}
}

livehttpheaders.live.HeaderInfoVisitor = function(oHttp){
    //dump("HeaderInfoVisitor\n");
    this.oHttp = oHttp;
    this.headers = new Array();
}
livehttpheaders.live.HeaderInfoVisitor.prototype = {
    oHttp: null,
    headers: null,
    getHttpResponseVersion: function(){
        var version = "1.z"; // Default value
        // Check if this is Mozilla v1.5a and more
        try {
            var maj = new Object();
            var min = new Object();
            this.oHttp.QueryInterface(Components.interfaces.nsIHttpChannelInternal);
            this.oHttp.getResponseVersion(maj, min);
            version = "" + maj.value + "." + min.value;
        } 
        catch (ex) {
        }
        return version;
    },
    getHttpRequestVersion: function(httpProxy){
        var version = "1.0"; // Default value for direct HTTP and proxy HTTP
        try {
            // This code is based on netwerk/protocol/http/src/nsHttpHandler.cpp (PrefsChanged)
            var pref = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService);
            pref = pref.getBranch("");
            // Now, get the value of the HTTP version fields
            if (httpProxy) {
                var tmp = pref.getCharPref("network.http.proxy.version");
                if (tmp == "1.1") 
                    version = tmp;
            }
            else {
                var tmp = pref.getCharPref("network.http.version");
                if (tmp == "1.1" || tmp == "0.9") 
                    version = tmp;
            }
        } 
        catch (ex) {
        }
        return version;
    },
    useHttpProxy: function(uri){
        // This code is based on netwerk/base/src/nsProtocolProxyService.cpp (ExamineForProxy)
        try {
            var pps = Components.classes["@mozilla.org/network/protocol-proxy-service;1"].getService().QueryInterface(Components.interfaces.nsIProtocolProxyService);
            
            // If a proxy is used for this url, we need to keep the host part
            if (typeof(pps.proxyEnabled) != "undefined") {
                // Mozilla up to 1.7
                if (pps.proxyEnabled && (pps.examineForProxy(uri) != null)) {
                    // Proxies are enabled.  Now, check if it is an HTTP proxy.
                    return this.isHttpProxy();
                }
            }
            else {
                // Firefox and Mozilla 1.8+
                if (pps.resolve(uri, pps.RESOLVE_NON_BLOCKING) != null) {
                    // Proxies are enabled.  Now, check if it is an HTTP proxy.
                    return this.isHttpProxy();
                }
            }
            return false; // No proxy or not HTTP Proxy
        } 
        catch (ex) {
            return null; // Error
        }
    },
    
    isHttpProxy: function(){
        // Check if an HTTP proxy is configured.
        var pref = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService);
        pref = pref.getBranch("");
        // Now, get the value of the HTTP proxy fields
        var http_host = pref.getCharPref("network.proxy.http");
        var http_port = pref.getIntPref("network.proxy.http_port");
        // network.proxy.http_port network.proxy.http
        if (http_host && http_port > 0) {
            return true; // HTTP Proxy
        }
        return false;
    },
    
    getPostData: function(oHttp){
        function postData(stream){
            // Scriptable Stream Constants
            const JS_SCRIPTABLEINPUTSTREAM_CID = "@mozilla.org/scriptableinputstream;1";
            const JS_SCRIPTABLEINPUTSTREAM = "nsIScriptableInputStream";
            const JS_ScriptableInputStream = new Components.Constructor(JS_SCRIPTABLEINPUTSTREAM_CID, JS_SCRIPTABLEINPUTSTREAM);
            // Create a scriptable stream
            this.seekablestream = stream;
            this.stream = new JS_ScriptableInputStream();
            this.stream.init(this.seekablestream);
            this.mode = this.FAST;
            
            // Check if the stream has headers
            try {
                this.seekablestream.QueryInterface(Components.interfaces.nsIMIMEInputStream);
                this.hasheaders = true;
                this.body = -1; // Must read header to find body
            } 
            catch (ex) {
                this.hasheaders = false;
                this.body = 0; // Body at the start of the stream
            }
        }
        postData.prototype = {
            NONE: 0,
            FAST: 1,
            SLOW: 2,
            rewind: function(){
                this.seekablestream.seek(0, 0);
            },
            tell: function(){
                return this.seekablestream.tell();
            },
            readLine: function(){
                var line = "";
                var size = 0;
                try {
                    size = this.stream.available();
                } 
                catch (ex) {
                    size = 0;
                }
                for (var i = 0; i < size; i++) {
                    var c = this.stream.read(1);
                    if (c == '\r') {
                    }
                    else 
                        if (c == '\n') {
                            break;
                        }
                        else {
                            line += c;
                        }
                }
                return line;
            },
            setMode: function(mode){
                if (mode < this.NONE && mode > this.SLOW) {
                    throw "postData: unsupported mode: " + this.mode;
                }
                this.mode = mode;
            },
            visitPostHeaders: function(visitor){
                this.rewind();
                if (!this.hasheaders) {
                    return;
                }
                var line = this.readLine();
                while (line) {
                    if (visitor) {
                        var tmp = line.split(/:\s?/);
                        visitor.visitHeader(tmp[0], tmp[1]);
                    }
                    line = this.readLine();
                }
                body = this.tell();
            },
            getPostBody: function(max){
                // Position the stream to the start of the body
                if (this.body < 0 || this.seekablestream.tell() != this.body) {
                    this.visitPostHeaders(null);
                }
                
                var size = 0;
                try {
                    size = this.stream.available();
                } 
                catch (ex) {
                    size = 0;
                }
                if (max && max >= 0 && max < size) 
                    size = max;
                
                var postString = "";
                try {
                    switch (this.mode) {
                        case this.NONE:
                            //Don't get any content
                            break;
                        case this.FAST:
                            //Get the content in one shot
                            if (size > 0) {
                                postString = this.stream.read(size);
                            }
                            break;
                        case this.SLOW:
                            //Must read octet by octet because of a bug in nsIMultiplexStream.cpp
                            //This is to avoid 'NS_BASE_STREAM_CLOSED' exception that may occurs
                            //See bug #188328.
                            for (var i = 0; i < size; i++) {
                                var c = this.stream.read(1);
                                c ? postString += c : postString += '\0';
                            }
                            break;
                    }
                } 
                catch (ex) {
                    //dump("Exception while getting POST CONTENT with mode "+this.mode+": "+ex+"\n");
                    return "" + ex;
                }
                finally {
                    // Need to close the stream after use.  
                    //this.seekablestream.close();
                    //this.stream.close();
                    //this.seekablestream.seek(2,0);
                    try {
                        this.stream.read(this.stream.available());
                    } 
                    catch (ex) {
                    }
                }
                return postString;
            }
        }
        
        // Get the postData stream from the Http Object 
        try {
            // Must change HttpChannel to UploadChannel to be able to access post data
            oHttp.QueryInterface(Components.interfaces.nsIUploadChannel);
            // Get the post data stream
            if (oHttp.uploadStream) {
                // Must change to SeekableStream to be able to rewind
                oHttp.uploadStream.QueryInterface(Components.interfaces.nsISeekableStream);
                // And return a postData object
                return new postData(oHttp.uploadStream);
            }
        } 
        catch (e) {
            //dump("POSTDATAEXCEPTION:"+e+"\n");
        }
        return null;
    },
    visitHeader: function(name, value){
        this.headers[name] = value;
    },
    visitRequest: function(){
        this.headers = new Array();
        var uri, note, ver;
        try {
        
            // Get the URL and get parts
            // Should I use  this.oHttp.URI.prePath and this.oHttp.URI.path to make
            // the URL ?  I still need to remove the '#' sign if present in 'path'
            var url = String(this.oHttp.URI.asciiSpec);
            
            // If an http proxy is used for this url, we need to keep the host part
            if (this.useHttpProxy(this.oHttp.URI) == true) {
                uri = url.match(/^(.*?\/\/[^\/]+\/[^#]*)/)[1];
                ver = this.getHttpRequestVersion(true);
            }
            else {
                uri = url.match(/^.*?\/\/[^\/]+(\/[^#]*)/)[1];
                ver = this.getHttpRequestVersion(false);
            }
        } 
        catch (ex) {
            //dump("PPS: cas5: " + ex + "\n");
            uri = String(this.oHttp.URI.asciiSpec);
            note = "Unsure about the precedent REQUEST uri";
        }
        this.headers["REQUEST"] = this.oHttp.requestMethod + " " +
        uri +
        " HTTP/" +
        ver;
        if (note) 
            this.headers["NOTE"] = note;
        this.oHttp.visitRequestHeaders(this);
        
        // There may be post data in the request
        var postData = this.getPostData(this.oHttp);
        if (postData) {
            postData.visitPostHeaders(this);
            this.visitHeader("POSTDATA", postData);
        }
        else {
            this.visitHeader("POSTDATA", null);
        }
        
        return this.headers;
    },
    visitResponse: function(){
        var ver = this.getHttpResponseVersion();
        this.headers = new Array();
        this.headers["RESPONSE"] = "HTTP/" + ver + " " + this.oHttp.responseStatus +
        " " +
        this.oHttp.responseStatusText;
        //this.headers["loadGroup"] = this.oHttp.loadGroup
        //this.headers["owner"] = this.oHttp.owner
        //this.headers["notificationCallbacks"] = this.oHttp.notificationCallbacks
        //if (this.oHttp.loadGroup) this.headers["loadGroup.ncb"] = this.oHttp.loadGroup.notificationCallbacks
        this.oHttp.visitResponseHeaders(this);
        return this.headers;
    }
}
